﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using Vux.SVCore.Network;

namespace Vux.SVCore.Client.Network
{
    public class SocketClient
    {
        // keep alive with endpoint
        private Socket _socket = null;
        // used to sync value type variables
        private static object _valueSyncObj = null;
        // used to sync the socket
        private static object _socketSyncObj = null;

        public SocketClient()
        {
            _socketSyncObj = new object();
            _valueSyncObj = new object();
            //_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        // event
        public event DataArrivalEventHandler DataArrival;
        public event ConnectedEventHandler Connected;
        public event DisconnectedEventHandler Disconnected;

        // raise event
        protected virtual void OnDataArrival(byte[] receivedData)
        {
            if (DataArrival != null)
            {
                DataArrival(receivedData);
            }
        }

        protected virtual void OnConnected()
        {
            if (Connected != null)
            {
                Connected();
            }
        }

        protected virtual void OnDisconnected()
        {
            if (Disconnected != null)
            {
                Disconnected();
            }
        }

        public bool IsConnected
        {
            get
            {
                #region Version 1
                //bool v_result = false;
                //if (_socket == null)
                //{
                //    return false;
                //}

                //lock (_socketSyncObj)
                //{
                //    v_result = _socket.Poll(1, SelectMode.SelectRead);
                //}
                //int v_temp;
                //lock (_socketSyncObj)
                //{
                //    v_temp = _socket.Available;
                //}
                ////In order to successfully test to see if the socket is connected, we AND the results of the Poll method and the avialable property.
                //if (v_result == true && v_temp == 0)
                //    return false;
                //return true;
                #endregion
                //var part1 = _socket.Poll(100, SelectMode.SelectRead);
                //var part2 = (_socket.Available == 0);
                //if (part1 & part2)
                //    return false;
                //else
                //    return true;

                return _socket == null ? false : _socket.Connected;
            }
        }

        private void ReceiveCallBack(IAsyncResult ip_aresult)
        {
            lock (_socketSyncObj)
            {
                try
                {
                    // Retrieve array of bytes
                    // Lấy dữ liệu trả về dưới dạng mảng byte
                    var state = (StateObject)ip_aresult.AsyncState;
                    // Get number of bytes received and also clean up resources that was used from beginReceive
                    // Trả về số byte nhận được từ server
                    var numBytes = _socket.EndReceive(ip_aresult);
                    if (numBytes > 0)
                    {
                        // Lấy dữ liệu đưa thêm vào _receivedData từ numBytes nhận được
                        var _receivedData = new byte[numBytes];
                        Array.Copy(state.buffer, 0, _receivedData, 0, numBytes);
                        OnDataArrival(_receivedData);
                        state.workSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallBack), state);
                    }
                }
                catch (SocketException ex)
                {
                    CloseConnection();
                }
                catch (Exception ex)
                {
                    CloseConnection();
                    Console.WriteLine();
                    Console.WriteLine("Error: {0}", ex.Message);
                    Console.WriteLine("Trace: {0}", ex.StackTrace);
                    Console.WriteLine("Data: {0}", ex.Data);
                    Console.WriteLine("Da ngat ket noi!");
                }
            }
        }

        private void Socket_Connected(IAsyncResult aResult)
        {
            if (IsConnected == false)
                return;
            // Tạo một đối tượng state để lưu trạng thái trao đổi với server.
            StateObject state = new StateObject();
            state.workSocket = _socket;
            // _client.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallBack), state);
            _socket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
            // Bắt đầu chờ nhận dữ liệu từ server, đồng thời phát sinh sự kiện OnConnected()
            OnConnected();
        }

        public void Connect(string hostNameOrAddress, int port)
        {
            try
            {
                var serverAddress = Dns.GetHostEntry(hostNameOrAddress).AddressList[0];
                this.Connect(serverAddress, port);
            }
            catch (Exception ex)
            {
                throw new Exception("Could not resolve Host name or Address", ex);
            }
        }

        public void Connect(IPAddress serverAddress, int port)
        {
            IPEndPoint ep = new IPEndPoint(serverAddress, port);
            this.Connect(ep);
        }

        public void Connect(IPEndPoint endPoint)
        {
            try
            {
                CloseConnection();
                _socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _socket.BeginConnect(endPoint, Socket_Connected, null);
            }
            catch (Exception ex)
            {
                throw new Exception("Không thể kết nối tới server", ex);
            }
        }

        public void Connect(Socket socket)
        {
            try
            {
                //this._socket = socket;
                //this._socket.BeginDisconnect(true, Socket_Connected, null);
                //Connect((IPEndPoint)socket.RemoteEndPoint);
                CloseConnection();
                StateObject state = new StateObject();
                state.workSocket = socket;
                _socket = socket;
                _socket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallBack), state);
                // Bắt đầu chờ nhận dữ liệu từ server, đồng thời phát sinh sự kiện OnConnected()
                OnConnected();
            }
            catch (Exception ex)
            {
                // Lỗi kết nối
                throw ex;
            }
        }

        public void Send(byte[] data)
        {
            if (IsConnected == false)
            {
                OnDisconnected();
                return;
            }
            else
            {
                lock (_socketSyncObj)
                {
                    _socket.Send(data, data.Length, SocketFlags.None);
                }
            }
        }

        public void CloseConnection()
        {
            lock (_socketSyncObj)
            {
                if (_socket != null)
                {
                    _socket.Close();
                    _socket = null;
                    OnDisconnected();
                }
            }

        }
    }

}
